import { book, chapter, content, search } from '../modules'
import db from './db'
import { existsSync, createWriteStream } from 'fs'
import { resolve as _resolve, join } from 'path'
import { promisify } from 'util'
import { URL } from 'url'
import fetch from 'node-fetch'
import { renderFile } from 'ejs'
import { GEN_MOBI, CALLBACK } from './channel'
import { spawn } from 'child_process'
import mkdirp from 'mkdirp'
import rimraf from 'rimraf'
import archiver from 'archiver'
import fs from 'fs/promises'

const { novels, sources, getBasePath } = db

const log = console

const { downloadBook, parseBook } = book
const { parseChapter } = chapter
const { downloadContent, parseContent } = content
const { downloadSearch, parseSearch } = search

const __static = _resolve(db.getBasePath(), '../static')

const app: any = {}

async function downloadBookMeta (source: any, bookUrl: string) {
  const doc = await downloadBook(source, bookUrl)
  const result = parseBook(source, doc)
  // if (!result.name) {
  //   throw new Error('解析失败')
  // }
  const chapters = parseChapter(source, doc)
  result.chapters = chapters
  result.bookUrl = bookUrl
  result.sourceId = source._id

  if (result.coverUrl && result.coverUrl.startsWith('/')) {
    result.coverUrl = `${source.bookSourceUrl}${result.coverUrl}`
  }
  console.log(result)
  return result
}

async function queryNovel (url: string) {
  let result = await novels().findOne({ bookUrl: url })
  if (result) {
    return result
  }
  const urlObj = new URL(url)
  const source = await sources().findOne({ bookSourceUrl: urlObj.origin })
  if (source) {
    result = await downloadBookMeta(source, url)
    result = await novels().insert(result)
    return result
  } else {
    throw new Error('找不到书源')
  }
}

async function downloadChapterMeta (novel: any, chapterId: string) {
  const novelId = novel._id

  const source: any = await sources().findOne({ _id: novel.sourceId })
  let downloadUrl
  if (chapterId.startsWith('/')) {
    downloadUrl = source.bookSourceUrl + chapterId
  } else {
    downloadUrl = novel.bookUrl + chapterId
  }
  log.info('start download', novelId, downloadUrl)
  const doc = await downloadContent(source, downloadUrl)
  log.info('download done', novelId, downloadUrl)
  log.info('start parse', novelId, downloadUrl)
  const result = parseContent(source, doc)
  log.info('parse done', novelId, downloadUrl)

  const chapterIndex = novel.chapters.findIndex((x: any) => x.chapterUrl === chapterId)
  const chapter = novel.chapters[chapterIndex]
  const html = `<h1 class="novel-title">${chapter.chapterName}</h1><div class="novel-content">${result.content}</div>`
  const bookDir = getBookDir(novelId)
  if (!existsSync(bookDir)) {
    await mkdirp(bookDir)
  }
  const fileName = getBookChapter(novelId, chapterId)
  if(result.content) {
    console.log('start write', fileName)
    await fs.writeFile(fileName, html, { encoding: 'utf8' })
    console.log('write complete', fileName)
    return html
  } else {
    await fs.unlink(fileName)
    return ""
  }
}

function getBookDir (novelId: string) {
  const bookDir = join(getBasePath(), '/data/book', novelId)
  return bookDir
}

function getBookChapter (novelId: string, url: string) {
  const chapterId = getChapterId(url)
  return join(getBookDir(novelId), chapterId)
}

function getChapterId (url: string) {
  const p1 = url.split('/')
  return p1[p1.length - 1]
}

function formatError (e: any, data?: any) {
  return { code: e.code || 500, error: e, message: e.message || '发生错误', data }
}

async function getNovelChapter (novel: any, chapterId: string, refresh: string) {
  let result
  const novelId = novel._id
  if (refresh || !existsSync(getBookChapter(novelId, chapterId))) {
    result = await downloadChapterMeta(novel, chapterId)
  } else {
    result = await fs.readFile(getBookChapter(novelId, chapterId), { encoding: 'utf8' })
  }

  return result
}

async function genMobi (arg: any, mainWindow: any) {
  const { id: novelId, kindlegen } = arg
  const bookdir = getBookDir(novelId)
  const tempBookdir = join(app.getPath('temp'), novelId)

  await promisify(rimraf)(tempBookdir)
  await mkdirp(tempBookdir)

  if (!existsSync(bookdir)) {
    throw new Error('请确认小说已缓存')
  }

  const bookInfo: any = await novels().findOne({ _id: novelId })

  if (!bookInfo) {
    throw new Error('未找到该小说')
  }

  if (bookInfo.coverUrl) {
    const res: any = await fetch(bookInfo.coverUrl).then(res => res.buffer())

    await fs.writeFile(join(tempBookdir, 'cover.jpg'), res)
  }

  const chapters = bookInfo.chapters

  const newChapters = []

  for (const chapter of chapters) {
    const fileName = getChapterId(chapter.chapterUrl)

    newChapters.push({
      title: chapter.chapterName,
      href: fileName,
      id: fileName.split('.')[0]
    })
    const html = await fs.readFile(getBookChapter(novelId, chapter.chapterUrl), { encoding: 'utf8' })
    try {
      const chapterHtml = await renderFile(join(__static, 'template/content.html'), {
        title: chapter.chapterName,
        content: html
      }, { async: true })
      await fs.writeFile(join(tempBookdir, fileName), chapterHtml)
    } catch (e) {
      console.log(e)
    }
  }

  const opf = await renderFile(join(__static, 'template/content.opf'), {
    chapters: newChapters,
    title: bookInfo.name,
    author: bookInfo.author,
    date: new Date(),
    description: bookInfo.introduce
  }, { async: true })
  await fs.writeFile(join(tempBookdir, 'content.opf'), opf)
  const ncx = await renderFile(join(__static, 'template/toc.ncx'), {
    chapters: newChapters,
    title: bookInfo.name,
    author: bookInfo.author
  }, { async: true })
  await fs.writeFile(join(tempBookdir, 'toc.ncx'), ncx)

  if (!existsSync(kindlegen)) {
    throw new Error('请确认kindlegen是否存在，点击右上角配置。')
  }

  return new Promise((resolve, reject) => {
    const ps = spawn(kindlegen, [join(tempBookdir, 'content.opf')])
    ps.stdout.on('data', (data) => {
      log.info(data.toString('utf8'))
      mainWindow.webContents.send(GEN_MOBI + CALLBACK, { code: 200, message: data.toString('utf8') })
    })

    ps.stderr.on('data', (data) => {
      log.error(data.toString('utf8'))
      mainWindow.webContents.send(GEN_MOBI + CALLBACK, formatError(new Error(data.toString('utf8'))))
    })

    ps.on('close', async (code) => {
      if (existsSync(join(tempBookdir, 'content.mobi'))) {
        const bookDir = join(app.getPath('desktop'), `${bookInfo.name}.mobi`)
        await fs.copyFile(join(tempBookdir, 'content.mobi'), bookDir)
        resolve(bookDir)
      } else {
        reject(code)
      }
    })
  })
}

async function genEpub (novelId: string, mainWindow: any) {
  const bookdir = getBookDir(novelId)
  const tempBookdir = join(app.getPath('temp'), novelId)

  await promisify(rimraf)(tempBookdir)
  await mkdirp(tempBookdir)

  if (!existsSync(bookdir)) {
    throw new Error('请确认小说已缓存')
  }

  const bookInfo: any = await novels().findOne({ _id: novelId })

  if (!bookInfo) {
    throw new Error('未找到该小说')
  }

  // copy META
  const metaDir = join(tempBookdir, 'META-INF')
  await mkdirp(metaDir)
  await fs.copyFile(join(__static, 'template/META-INF/container.xml'), join(metaDir, 'container.xml'))

  // copy OEBPS
  const oebpsDir = join(tempBookdir, 'OEBPS')
  await mkdirp(oebpsDir)

  if (bookInfo.coverUrl) {
    const res: any = await fetch(bookInfo.coverUrl).then(res => res.buffer())

    await fs.writeFile(join(oebpsDir, 'cover.jpg'), res)
  }

  const chapters = bookInfo.chapters

  const newChapters = []

  for (const chapter of chapters) {
    const fileName = getChapterId(chapter.chapterUrl)

    newChapters.push({
      title: chapter.chapterName,
      href: fileName,
      id: fileName.split('.')[0]
    })
    const chapterFile = getBookChapter(novelId, chapter.chapterUrl)
    if (!existsSync(chapterFile)) {
      log.error('file not found ' + chapterFile)
      throw new Error('章节未缓存：' + chapter.chapterName)
    }
    log.info('read file ' + chapterFile)
    const html = await fs.readFile(chapterFile, { encoding: 'utf8' })
    const chapterHtml = await renderFile(join(__static, 'template/content.html'), {
      title: chapter.chapterName,
      content: html
    }, { async: true })
    await fs.writeFile(join(oebpsDir, fileName), chapterHtml)
  }

  const opf = await renderFile(join(__static, 'template/content.opf'), {
    chapters: newChapters,
    title: bookInfo.name,
    author: bookInfo.author,
    date: new Date(),
    description: bookInfo.introduce
  }, { async: true })
  await fs.writeFile(join(oebpsDir, 'content.opf'), opf)
  const ncx = await renderFile(join(__static, 'template/toc.ncx'), {
    chapters: newChapters,
    title: bookInfo.name,
    author: bookInfo.author
  }, { async: true })
  await fs.writeFile(join(oebpsDir, 'toc.ncx'), ncx)

  return new Promise((resolve, reject) => {
    const tempEpub = join(app.getPath('temp'), `${bookInfo.name}.zip`)

    const zip = archiver('zip', {
      zlib: {
        level: 9
      }
    })
    const output = createWriteStream(tempEpub)
    console.log('Zipping temp dir to', tempEpub)
    zip.append('application/epub+zip', {
      store: true,
      name: 'mimetype'
    })
    zip.directory(tempBookdir + '/META-INF', 'META-INF')
    zip.directory(tempBookdir + '/OEBPS', 'OEBPS')
    zip.pipe(output)
    zip.on('end', async function () {
      console.log('Done zipping, clearing temp dir...')
      const bookDir = join(app.getPath('desktop'), `${bookInfo.name}.epub`)
      log.info('copy', `${bookInfo.name}.epub`)
      await fs.copyFile(tempEpub, bookDir)
      resolve(bookDir)
    })
    zip.on('error', function (err) {
      reject(err)
    })
    zip.finalize()
  })
}

async function searchNovel (source: any, searchKey: string) {
  const result = await downloadSearch(source, searchKey)
  return parseSearch(source, result)
}

export {
  downloadBookMeta,
  queryNovel,
  downloadChapterMeta,
  getBookDir,
  getBookChapter,
  getChapterId,
  formatError,
  getNovelChapter,
  genMobi,
  genEpub,
  searchNovel
}
